// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PublishingPageHelper.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Defines the PublishingPageHelper type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.SharePoint.Moss.Publishing
{
    using System;

    using Collaboris.SharePoint.Moss;
    using Collaboris.SharePoint.Moss.Resources;
    using Collaboris.Sharepoint.WSS.ContentTypes;
    using Collaboris.Utils;

    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Publishing;

    /// <summary>
    /// Adds helper methods to the Sharepoint <see cref="PublishingPage"/> class.
    /// </summary>
    public class PublishingPageHelper : MossHelperBase
    {
        /// <summary>
        /// Creates the publishing page.
        /// </summary>
        /// <param name="pubWeb">The publishing web where the page will be created.</param>
        /// <param name="name">The name of the page.</param>
        /// <param name="pageLayoutName">Name of the page layout.</param>
        /// <param name="setAsDefault">if set to <c>true</c> [set as default/Welcome Page].</param>
        /// <param name="addVersionIfExists">if set to <c>true</c> [add version if exists].</param>
        /// <param name="deleteIfExists">if set to <c>true</c> [delete if exists].</param>
        /// <returns>
        /// Returns a PublishingPage reference to the newly created page.
        /// </returns>
        public static PublishingPage CreatePublishingPage(
                PublishingWeb pubWeb, string name, string pageLayoutName, bool setAsDefault, bool addVersionIfExists, bool deleteIfExists)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "CreatePublishingPage");

            // get the page layout
            ValidationHelper.VerifyStringArgument(pageLayoutName, "pageLayoutName");

            PageLayout layout = PageLayoutHelper.GetPageLayout(pubWeb, pageLayoutName);
            
            ValidationHelper.VerifyObjectArgument(layout, "layout");

            // now lets call the overload
            PublishingPage page = CreatePublishingPage(pubWeb, name, layout, setAsDefault, addVersionIfExists, false, deleteIfExists);

            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "CreatePublishingPage");
            return page;
        }

        /// <summary>
        /// Creates the publishing page.
        /// </summary>
        /// <param name="pubWeb">The publishing web where the page will be added.</param>
        /// <param name="name">The name of the page including extension (e.g. "default.aspx"</param>
        /// <param name="pageLayout">The page layout to base the page upon.</param>
        /// <param name="setAsDefault">if set to <c>true</c> the page will become the default for the publishing web.</param>
        /// <param name="addVersionIfExists">The add Version If Exists.</param>
        /// <param name="forceUpdate">The force Update.</param>
        /// <param name="deleteIfExists">The delete If Exists.</param>
        /// <returns>
        /// PublishingPage if it gets created; otherwise null
        /// </returns>
        /// <remarks>
        /// if you set deleteIfExists and addVersionIfExists to True, then the file will be deleted
        /// </remarks>
        public static PublishingPage CreatePublishingPage(
                PublishingWeb pubWeb,
                string name,
                PageLayout pageLayout,
                bool setAsDefault,
                bool addVersionIfExists,
                bool forceUpdate,
                bool deleteIfExists)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "CreatePublishingPage");

            ValidationHelper.VerifyObjectArgument(pubWeb, "pubWeb");
            ValidationHelper.VerifyObjectArgument(pageLayout, "pageLayout");
            ValidationHelper.VerifyStringArgument(name, "name");
            PublishingPage newPage = null;

            TraceHelper.TraceVerbose(TraceSwitch, "PublishingPageHelper", Logging.AddingNewPublishingPageCalledThis + name);

            pubWeb.Web.AllowUnsafeUpdates = true;

            PublishingPage existPage = GetPage(name, pubWeb);

            if (existPage != null)
            {
                if (deleteIfExists)
                {
                    TraceHelper.TraceVerbose(TraceSwitch, "PublishingPageHelper", "Page Exists, so deleting it");
                    if (IsDefault(pubWeb, existPage.ListItem.File))
                    {
                        // make another page
                        throw new Exception(String.Format("Cannot delete '{0}' as its the default page", existPage.ListItem.Url));
                    }

                    existPage.ListItem.Delete();
                }
                else
                {                
                    if (addVersionIfExists)
                    {
                        // Checks out the page and sets the Page Layout
                        newPage = SetPageLayout(pubWeb, pageLayout, existPage);
                    }
                    else 
                    {
                        TraceHelper.TraceInfo(TraceSwitch, "PublishingPageHelper", "Cannot amend page, as 'AddVersionIfExists' is set to false");
                        TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "CreatePublishingPage");
                        return null;
                    }
                }
            }

            if (newPage == null)
            {
                TraceHelper.TraceVerbose(TraceSwitch, "PublishingPageHelper", "Page Doesnt exist, so creating new");
                newPage = pubWeb.GetPublishingPages().Add(name, pageLayout);                
            }

            if (setAsDefault)
            {
                SetDefaultPage(pubWeb, newPage.ListItem.File, false);
            }

            if (forceUpdate)
            {
                newPage.ListItem.UpdateOverwriteVersion();
                pubWeb.Update();
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "CreatePublishingPage");
            return newPage;
        }

        /// <summary>
        /// Sets the page layout.
        /// </summary>
        /// <param name="pubWeb">The publishing web.</param>
        /// <param name="pageLayout">The page layout.</param>
        /// <param name="existPage">The exist page.</param>
        /// <returns>A PublishingPage</returns>
        private static PublishingPage SetPageLayout(PublishingWeb pubWeb, PageLayout pageLayout, PublishingPage existPage)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "SetPageLayout");

            TraceHelper.TraceVerbose(TraceSwitch, "PublishingPageHelper", "Page Exists, so amending the page Layout");
            if (existPage.ListItem.File.CheckOutStatus != SPFile.SPCheckOutStatus.None)
            {
                TraceHelper.TraceVerbose(TraceSwitch, "PublishingPageHelper", "Publishing a File as its checked out");
                existPage.CheckIn("Checked in by SAF");
            }

            existPage.CheckOut();

            // amended this to get around the bug where 
            // pages point to the wrong layouts.
            try
            {
                existPage.Layout = pageLayout;
            }
            catch (Exception)
            {
                // Attempt to fix
                MakePageLayoutFix(pubWeb, pageLayout, existPage, true);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "SetPageLayout");

            return existPage;
        }

        /// <summary>
        /// Makes the page layout relative not absolute to potentially
        /// the wrong server.
        /// </summary>
        /// <param name="pubWeb">The publishing web.</param>
        /// <param name="pageLayout">The page layout.</param>
        /// <param name="existPage">The exist page.</param>
        /// <param name="doUpdate">if set to <c>true</c> [do update].</param>
        private static void MakePageLayoutFix(PublishingWeb pubWeb, PageLayout pageLayout, PublishingPage existPage, bool doUpdate)
        {
            if (existPage.ListItem[FieldId.PageLayout] != null)
            {
                SPFieldUrlValue url = new SPFieldUrlValue(existPage.ListItem[FieldId.PageLayout].ToString());
                if (string.IsNullOrEmpty(url.Url) || url.Url.IndexOf("/_catalogs/") < 0)
                {
                    url.Url = pageLayout.ServerRelativeUrl;
                    url.Description = pubWeb.Web.Site.MakeFullUrl(pageLayout.ServerRelativeUrl);                    
                }
                else
                {
                    string newUrl = pubWeb.Web.Site.ServerRelativeUrl.TrimEnd('/') +
                                    url.Url.Substring(url.Url.IndexOf("/_catalogs/"));

                    string newDesc = pubWeb.Web.Site.MakeFullUrl(newUrl);

                    if (url.Url.ToLowerInvariant() != newUrl.ToLowerInvariant())
                    {
                        url.Url = newUrl;
                        url.Description = newDesc;
                    }
                }

                existPage.ListItem[FieldId.PageLayout] = url;
                if (doUpdate)
                {
                    existPage.ListItem.SystemUpdate();
                }
            }
        }

        /// <summary>
        /// Deletes the publishing page.
        /// </summary>
        /// <param name="page">The page to delete</param>        
        /// <param name="verifyDelete">if set to <c>true</c> an addtional check will be made to see if it has gone.</param>
        /// <returns>
        /// if verifyDelete is true, then returns true if its gone; else false;
        /// if verifyDelete is false; alwasys returns true.
        /// </returns>
        public static bool DeletePublishingPage(PublishingPage page, bool verifyDelete)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "DeletePublishingPage");

            // Validate the arguments            
            ValidationHelper.VerifyObjectArgument(page, "page");

            PublishingWeb pubWeb = page.PublishingWeb;

            TraceHelper.TraceVerbose(TraceSwitch, "PublishingPageHelper", Logging.DeletingPublishingPage + page.Name);

            // Go delete it
            page.ListItem.Delete();
            page.PublishingWeb.Update();

            // If we want to verify that it has gone! 
            if (verifyDelete)
            {
                TraceHelper.TraceVerbose(TraceSwitch, "PublishingPageHelper", Logging.CheckingToSeeIfThisPageExists + page.Name);
                return ! PageExists(page.Name, pubWeb);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "DeletePublishingPage");
            return true;
        }

        /// <summary>
        /// Gets the PublishingPage.
        /// </summary>
        /// <param name="publishingPage">The publishing page.</param>
        /// <param name="publishingWeb">The publishing web.</param>
        /// <remarks>The match for the publishingpage is case insensitive</remarks>
        /// <returns>PublishingPage if found; otherwise false.</returns>
        /// <exception cref="ArgumentNullException">Throws if publishingWeb is null</exception>
        /// <exception cref="ArgumentNullException">Throws if publishingPage is null</exception>
        public static PublishingPage GetPage(string publishingPage, PublishingWeb publishingWeb)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "GetPage");

            // Validate the arguments
            ValidationHelper.VerifyStringArgument(publishingPage, "publishingPage");
            ValidationHelper.VerifyObjectArgument(publishingWeb, "publishingWeb");

            TraceHelper.TraceVerbose(TraceSwitch, "PublishingPageHelper", Logging.GettingPublishingPage + publishingPage);

            foreach (PublishingPage page in publishingWeb.GetPublishingPages())
            {
                TraceHelper.TraceVerbose(
                        TraceSwitch,
                        "PublishingPageHelper",
                        Logging.CheckingPage + publishingPage + Logging.ForAMatchWithThisPage + page.ListItem.File.Name);

                if (page.ListItem.File.Name.ToUpper() == publishingPage.ToUpper())
                {
                    return page;
                }
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "GetPage");

            return null;
        }

        /// <summary>
        /// Check to see if the Page exists.
        /// </summary>
        /// <param name="publishingPage">The publishing page.</param>
        /// <param name="publishingWeb">The publishing web.</param>
        /// <returns>true if the page Exists; otherwise false</returns>
        /// <remarks>The match for the publishingpage is case insensitive</remarks>
        /// <exception cref="ArgumentNullException">Throws if publishingWeb is null</exception>
        /// <exception cref="ArgumentNullException">Throws if publishingPage is null</exception>
        public static bool PageExists(string publishingPage, PublishingWeb publishingWeb)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "PageExists");

            ValidationHelper.VerifyStringArgument(publishingPage, "publishingPage");
            ValidationHelper.VerifyObjectArgument(publishingWeb, "publishingWeb");
            
            bool rv = false;

            TraceHelper.TraceVerbose(
                    TraceSwitch,
                    "PublishingPageHelper",
                    Logging.CheckingToSeeIfThisPage + publishingPage + Logging.ExistsInThisWeb + publishingWeb.Name);

            PublishingPage page = GetPage(publishingPage, publishingWeb);
            if (page != null)
            {
                TraceHelper.TraceVerbose(TraceSwitch, "PublishingPageHelper", publishingPage + Logging.DoesNotExist);
            }
            else
            {
                TraceHelper.TraceVerbose(TraceSwitch, "PublishingPageHelper", publishingPage + Logging.DoesNotExist);
                rv = true;
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "PageExists");

            return rv;
        }

        /// <summary>
        /// Sets the 'file' passed in as the Default Page
        /// </summary>
        /// <param name="pubWeb"></param>
        /// <param name="file"></param>
        /// <param name="updateWeb"></param>
        public static void SetDefaultPage(PublishingWeb pubWeb, SPFile file, bool updateWeb)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "SetDefaultPage");

            ValidationHelper.VerifyObjectArgument(pubWeb, "pubWeb");
            ValidationHelper.VerifyObjectArgument(file, "file");

            TraceHelper.TraceVerbose(
                    TraceSwitch,
                    "PublishingPageHelper",
                    Logging.SettingTheDefaultPageInThePublishingWeb + pubWeb.Name + ", File : '" + file.Name + "'");

            pubWeb.DefaultPage = file;

            if (updateWeb)
                pubWeb.Update();

            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "SetDefaultPage");
        }


        /// <summary>
        /// Gets the 'publishingPage'acting as the Default Page
        /// </summary>
        /// <param name="pubWeb"></param>
        public static PublishingPage GetDefaultPage(PublishingWeb pubWeb)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "SetDefaultPage");

            ValidationHelper.VerifyObjectArgument(pubWeb, "pubWeb");
            
            PublishingPage defaultPage = PublishingPage.GetPublishingPage(pubWeb.DefaultPage.Item);
            
            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "SetDefaultPage");

            return defaultPage;

        }

        /// <summary>
        /// Determines if the page is welcome page.
        /// </summary>
        /// <param name="pubWeb">The pub web.</param>
        /// <param name="file">The file.</param>
        /// <returns>
        /// 	<c>true</c> if the specified pub web is default; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsDefault(PublishingWeb pubWeb, SPFile file)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "IsDefault");

            bool rv = false;
            ValidationHelper.VerifyObjectArgument(pubWeb, "pubWeb");
            ValidationHelper.VerifyObjectArgument(file, "file");

            if (pubWeb.DefaultPage.Url == file.Url)
                rv = true;
            
            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "IsDefault");

            return rv;
        }

        /// <summary>
        /// Sets the PublishingPage's Page Layout
        /// </summary>
        /// <param name="page">The page.</param>
        /// <param name="layout">The layout.</param>
        public static void SetPageLayout(PublishingPage page, PageLayout layout)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "SetPageLayout");
            ValidationHelper.VerifyObjectArgument(page, "page");
            ValidationHelper.VerifyObjectArgument(layout, "layout");

            // Set the Page layout
            TraceHelper.TraceVerbose(
                    TraceSwitch,
                    "PublishingPageHelper",
                    "Setting the page layout for this Page : " + page.Name + ", Page Layout : '" + layout.Name + "'");

            page.Layout = layout;
            page.Update();

            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "SetPageLayout");
        }

        /// <summary>
        /// Creates the redirect page and returns it.
        /// </summary>
        /// <param name="publishingWeb">
        /// The publishing Web.
        /// </param>
        /// <param name="redirectUrl">
        /// The redirect URL.
        /// </param>
        /// <param name="redirectUrlDescription">
        /// The redirect Url Description.
        /// </param>
        /// <param name="pageName">
        /// The name of the page e.g 'Default.aspx'
        /// </param>
        /// <param name="setAsDefault">
        /// The set As Default.
        /// </param>
        /// <param name="addVersion">
        /// The add Version.
        /// </param>
        /// <param name="customContentTypeId">
        /// The custom Content Type Id.
        /// </param>
        /// <param name="customPageLayout">
        /// The custom Page Layout.
        /// </param>
        /// <param name="customUrlDescFieldName">
        /// The custom Url Desc Field Name.
        /// </param>
        /// <param name="urlFieldName">
        /// The url Field Name.
        /// </param>
        /// <returns>
        /// SPFile representing the redirect page
        /// </returns>
        public static PublishingPage CreateRedirectPage(
                PublishingWeb publishingWeb,
                string redirectUrl,
                string redirectUrlDescription,
                string pageName,
                bool setAsDefault,
                bool addVersion,
                string customContentTypeId,
                string customPageLayout,
                string customUrlDescFieldName,
                string urlFieldName)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "CreateRedirectPage");

            ValidationHelper.VerifyObjectArgument(publishingWeb, "publishingWeb");
            ValidationHelper.VerifyStringArgument(redirectUrl, "redirectUrl");
            ValidationHelper.VerifyStringArgument(pageName, "pageName");
            bool customMode = true;

            if (string.IsNullOrEmpty(redirectUrlDescription))
                redirectUrlDescription = redirectUrl;

            SPContentTypeId redirecContentId = GetContentTypeIdForRedirect(ref customContentTypeId);

            if (string.IsNullOrEmpty(urlFieldName))
            {
                customMode = false;
                urlFieldName = "Redirect URL";
            }

            TraceHelper.TraceVerbose(
                    TraceSwitch,
                    "PublishingPageHelper",
                    "Getting the Redirect Page Layout from the '{0}'s available layouts",
                    publishingWeb.Url);

            PageLayout pageLayout = GetPageLayoutForRedirect(publishingWeb, ref redirecContentId, customPageLayout);

            TraceHelper.TraceVerbose(
                    TraceSwitch,
                    "PublishingPageHelper",
                    "Creating the Redirect Page from the '{0}'s available layouts. Default? '{1}'",
                    pageName,
                    setAsDefault.ToString());

            PublishingPage newPage = CreatePublishingPage(publishingWeb, pageName, pageLayout, setAsDefault, addVersion, false, false);

            SPContentTypeHelper.AddContentTypeToList(publishingWeb.Web, newPage.ListItem.ParentList, redirecContentId);
            
            newPage.ListItem["Content Type ID"] = redirecContentId;
            newPage.ListItem.SystemUpdate();

            WriteToRedirectField(redirectUrl, redirectUrlDescription, customUrlDescFieldName, urlFieldName, customMode, newPage);

            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "CreateRedirectPage");
            return newPage;
        }

        /// <summary>
        /// Gets the page layout for redirect.
        /// </summary>
        /// <param name="publishingWeb">The publishing web.</param>
        /// <param name="redirecContentId">The redirec content id.</param>
        /// <param name="pageLayoutName">Name of the page layout.</param>
        /// <returns></returns>
        private static PageLayout GetPageLayoutForRedirect(
                PublishingWeb publishingWeb, ref SPContentTypeId redirecContentId, string pageLayoutName)
        {
            PageLayout[] layouts = publishingWeb.GetAvailablePageLayouts(redirecContentId);
            PageLayout pageLayout = null;
            if (layouts.Length < 1)
            {
                string msg =
                        string.Format(
                                "The Redirect Page is not 'available' for this web '{0}'."
                                + " Please add it as an 'Available Page Layout'",
                                publishingWeb.Url);

                TraceHelper.TraceWarning(TraceSwitch, "PublishingPageHelper", msg);
            }
            else
            {
                pageLayout = layouts[0];
            }

            // attempt to get it on name
            if (pageLayout == null && ! string.IsNullOrEmpty(pageLayoutName))
            {
                TraceHelper.TraceVerbose(TraceSwitch, "PublishingPageHelper", "Attempting to find this Page Layout '{0}'", pageLayoutName);
                pageLayout = PageLayoutHelper.GetPageLayout(publishingWeb, pageLayoutName);
            }
            
            ValidationHelper.VerifyObjectArgument(pageLayout, "pageLayout");
            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "GetPageLayoutForRedirect");

            return pageLayout;
        }

        /// <summary>
        /// Writes to redirect field.
        /// </summary>
        /// <param name="redirectUrl">The redirect URL.</param>
        /// <param name="redirectUrlDescription">The redirect URL description.</param>
        /// <param name="customUrlDescFieldName">Name of the custom URL desc field.</param>
        /// <param name="urlFieldName">Name of the URL field.</param>
        /// <param name="customMode">if set to <c>true</c> [custom mode].</param>
        /// <param name="newPage">The new page.</param>
        private static void WriteToRedirectField(
                string redirectUrl,
                string redirectUrlDescription,
                string customUrlDescFieldName,
                string urlFieldName,
                bool customMode,
                PublishingPage newPage)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "WriteToRedirectField");

            if (newPage.Fields.ContainsField(urlFieldName))
            {
                if (!customMode)
                {
                    // using the standard sharepoint way
                    SPFieldUrlValue value = new SPFieldUrlValue();
                    value.Url = redirectUrl;
                    value.Description = redirectUrlDescription;
                    newPage.ListItem[urlFieldName] = value;
                }
                else
                {
                    newPage.ListItem[urlFieldName] = redirectUrl;
                    if (!string.IsNullOrEmpty(customUrlDescFieldName))
                    {
                        if (newPage.Fields.ContainsField(customUrlDescFieldName))
                        {
                            newPage.ListItem[customUrlDescFieldName] = redirectUrlDescription;
                        }
                    }
                }
                
                newPage.ListItem.UpdateOverwriteVersion();
            }
            else
            {
                TraceHelper.TraceWarning(TraceSwitch, "PublishingPageHelper", "Unable to access the 'Redirect URL' field");
            }
        }

        /// <summary>
        /// Gets the content type id for redirect.
        /// </summary>
        /// <param name="customContentTypeId">The custom content type id.</param>
        /// <returns></returns>
        private static SPContentTypeId GetContentTypeIdForRedirect(ref string customContentTypeId)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "PublishingPageHelper", "GetContentTypeIdForRedirect");

            if (string.IsNullOrEmpty(customContentTypeId))
            {
                customContentTypeId  = "0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900FD0E870BA06948879DBD5F9813CD8799";
            } 
            else
            {
                TraceHelper.TraceInfo(TraceSwitch, "PublishingPageHelper", "Custom Redirect Properties have been passed");
            }
            
            SPContentTypeId redirecContentId = new 
                    SPContentTypeId(customContentTypeId);

            TraceHelper.TraceMethodEnd(TraceSwitch, "PublishingPageHelper", "GetContentTypeIdForRedirect");
            return redirecContentId;
        }
    }
}